using System;
using System.IO;

namespace PWEmu.ZLib
{
    /// <summary>
    /// ZLib input stream.
    /// </summary>
    public class ZInputStream : BinaryReader
    {
        /// <summary>Gets the total number of bytes input so far.</summary>
        virtual public long TotalIn { get { return _zstream.totalIn; } }

        /// <summary>Gets the total number of bytes output so far.</summary>
        virtual public long TotalOut { get { return _zstream.totalOut; } }

        /// <summary></summary>
        protected ZStream _zstream = new ZStream();

        /// <summary></summary>
        protected int _bufSize = 512;

        /// <summary></summary>
        protected int _flush;

        /// <summary></summary>
        protected byte[] _buf;

        /// <summary></summary>
        protected byte[] _buf1 = new byte[1];

        /// <summary></summary>
        protected bool _compress;

        /// <summary></summary>
        internal Stream _inputStream = null;

        /// <summary></summary>
        internal bool _nomoreinput = false;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inputStream"></param>
        public ZInputStream(Stream inputStream)
            : base(inputStream)
        {
            InitBlock();
            _inputStream = inputStream;
            _zstream.InflateInit();
            _compress = false;
            _zstream.nextIn = _buf;
            _zstream.nextInIndex = 0;
            _zstream.availableIn = 0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inRenamed"></param>
        /// <param name="level"></param>
        public ZInputStream(Stream inRenamed, int level)
            : base(inRenamed)
        {
            InitBlock();
            _inputStream = inRenamed;
            _zstream.DeflateInit(level);
            _compress = true;
            _zstream.nextIn = _buf;
            _zstream.nextInIndex = 0;
            _zstream.availableIn = 0;
        }

        /// <summary>
        /// 
        /// </summary>
        internal void InitBlock()
        {
            _flush = ZLibConst.StreamStatus.Z_NO_FLUSH;
            _buf = new byte[_bufSize];
        }

        /// <summary>
        /// 
        /// </summary>
        virtual public int FlushMode
        {
            get { return _flush; }
            set { _flush = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int Read()
        {
            if (Read(_buf1, 0, 1) == -1)
                return (-1);
            return (_buf1[0] & 0xFF);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="b"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public override int Read(byte[] b, int offset, int length)
        {
            if (length == 0)
                return (0);
            int err;
            _zstream.nextOut = b;
            _zstream.nextOutIndex = offset;
            _zstream.availableOut = length;
            do
            {
                if ((_zstream.availableIn == 0) && (!_nomoreinput))
                {
                    // if buffer is empty and more input is avaiable, refill it
                    _zstream.nextInIndex = 0;
                    _zstream.availableIn = SupportClass.ReadInput(_inputStream, _buf, 0, _bufSize); //(bufsize<z.avail_out ? bufsize : z.avail_out));
                    if (_zstream.availableIn == -1)
                    {
                        _zstream.availableIn = 0;
                        _nomoreinput = true;
                    }
                }
                if (_compress)
                    err = _zstream.Deflate(_flush);
                else
                    err = _zstream.Inflate(_flush);
                if (_nomoreinput && (err == ZLibConst.Status.Z_BUF_ERROR))
                    return (-1);
                if (err != ZLibConst.Status.Z_OK && err != ZLibConst.Status.Z_STREAM_END)
                    throw new ZStreamException((_compress ? "De" : "In") + "flating: " + _zstream.message);
                if (_nomoreinput && (_zstream.availableOut == length))
                    return (-1);
            }
            while (_zstream.availableOut == length && err == ZLibConst.Status.Z_OK);
            return (length - _zstream.availableOut);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public long Skip(long n)
        {
            int len = 512;
            if (n < len)
                len = (int)n;
            byte[] tmp = new byte[len];
            return ((long)SupportClass.ReadInput(BaseStream, tmp, 0, tmp.Length));
        }

        /// <summary>
        /// 
        /// </summary>
        public override void Close()
        {
            _inputStream.Close();
        }
    }
}